// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package wasm

import (
	"bytes"
	"cmd/internal/objabi"
	"cmd/link/internal/ld"
	"cmd/link/internal/loader"
	"cmd/link/internal/sym"
	"internal/buildcfg"
	"io"
	"regexp"
)

const (
	I32 = 0x7F
	I64 = 0x7E
	F32 = 0x7D
	F64 = 0x7C
)

const (
	sectionCustom   = 0
	sectionType     = 1
	sectionImport   = 2
	sectionFunction = 3
	sectionTable    = 4
	sectionMemory   = 5
	sectionGlobal   = 6
	sectionExport   = 7
	sectionStart    = 8
	sectionElement  = 9
	sectionCode     = 10
	sectionData     = 11
)

// funcValueOffset是函数的PC_F值与WebAssembly中函数的索引之间的偏移量
const funcValueOffset = 0x1000 // TODO（neelance）：使函数地址与堆地址配合使用

func gentext(ctxt *ld.Link, ldr *loader.Loader) {
}

type wasmFunc struct {
	Name string
	Type uint32
	Code []byte
}

type wasmFuncType struct {
	Params  []byte
	Results []byte
}

var wasmFuncTypes = map[string]*wasmFuncType{
	"_rt0_wasm_js":           {Params: []byte{}},                                         // None
	"wasm_export_run":        {Params: []byte{I32, I32}},                                 // argc，argv
	"wasm_export_resume":     {Params: []byte{}},                                         // None
	"wasm_export_getsp":      {Results: []byte{I32}},                                     // 服务提供商
	"wasm_pc_f_loop":         {Params: []byte{}},                                         // None
	"runtime.wasmMove":       {Params: []byte{I32, I32, I32}},                            // dst、src、len
	"runtime.wasmZero":       {Params: []byte{I32, I32}},                                 // ptr，len
	"runtime.wasmDiv":        {Params: []byte{I64, I64}, Results: []byte{I64}},           // x、 y->x/y
	"runtime.wasmTruncS":     {Params: []byte{F64}, Results: []byte{I64}},                // x->int（x）
	"runtime.wasmTruncU":     {Params: []byte{F64}, Results: []byte{I64}},                // x->uint（x）
	"runtime.gcWriteBarrier": {Params: []byte{I64, I64}},                                 // ptr，val
	"cmpbody":                {Params: []byte{I64, I64, I64, I64}, Results: []byte{I64}}, // a、 阿伦，b，布伦->-1/0/1
	"memeqbody":              {Params: []byte{I64, I64, I64}, Results: []byte{I64}},      // a、 b，len->0/1
	"memcmp":                 {Params: []byte{I32, I32, I32}, Results: []byte{I32}},      // a、 b，len-><0/0/>0
	"memchr":                 {Params: []byte{I32, I32, I32}, Results: []byte{I32}},      // s、 c，len->index
}

func assignAddress(ldr *loader.Loader, sect *sym.Section, n int, s loader.Sym, va uint64, isTramp bool) (*sym.Section, int, uint64) {
	// WebAssembly函数与线性内存不在同一地址空间中。
	// 相反，WebAssembly会自动分配索引。导入的功能（“导入”部分）
	// 索引为0到n。然后是本机函数（“函数”和“代码”部分）
	// 指数为n+1及以下。
	// None
	// 以下规则描述了wasm如何处理函数索引和地址：
	// PC_F=funcValueOffset+WebAssembly函数索引（不包括导入）
	// s、 值=PC=PC_F<<16+PC_B
	// None
	// funcValueOffset是避免与预期冲突所必需的
	// Go运行时有大约个函数地址。
	// 字段“s.Value”对应于运行时PC的概念。
	// 但是，没有PC寄存器，只有PC_F和PC_B。PC_F表示功能，
	// PC_B该函数内的恢复点。函数项的PC_B=0。
	ldr.SetSymSect(s, sect)
	ldr.SetSymValue(s, int64(funcValueOffset+va/ld.MINFUNC)<<16) // va从零开始
	va += uint64(ld.MINFUNC)
	return sect, n, va
}

type wasmDataSect struct {
	sect *sym.Section
	data []byte
}

var dataSects []wasmDataSect

func asmb(ctxt *ld.Link, ldr *loader.Loader) {
	sections := []*sym.Section{
		ldr.SymSect(ldr.Lookup("runtime.rodata", 0)),
		ldr.SymSect(ldr.Lookup("runtime.typelink", 0)),
		ldr.SymSect(ldr.Lookup("runtime.itablink", 0)),
		ldr.SymSect(ldr.Lookup("runtime.symtab", 0)),
		ldr.SymSect(ldr.Lookup("runtime.pclntab", 0)),
		ldr.SymSect(ldr.Lookup("runtime.noptrdata", 0)),
		ldr.SymSect(ldr.Lookup("runtime.data", 0)),
	}

	dataSects = make([]wasmDataSect, len(sections))
	for i, sect := range sections {
		data := ld.DatblkBytes(ctxt, int64(sect.Vaddr), int64(sect.Length))
		dataSects[i] = wasmDataSect{sect, data}
	}
}

// asmb编写最终的WebAssembly模块二进制文件。
// 规范：https:
func asmb2(ctxt *ld.Link, ldr *loader.Loader) {
	types := []*wasmFuncType{
		// 对于正常Go功能，单个参数为PC_B，
		// 返回值为
		// 如果函数返回正常或
		// 如果堆叠需要展开，则为1。
		{Params: []byte{I32}, Results: []byte{I32}},
	}

	// 收集主机导入（从WebAssembly主机导入的函数，通常为JavaScript）
	hostImports := []*wasmFunc{
		{
			Name: "debug",
			Type: lookupType(&wasmFuncType{Params: []byte{I32}}, &types),
		},
	}
	hostImportMap := make(map[loader.Sym]int64)
	for _, fn := range ctxt.Textp {
		relocs := ldr.Relocs(fn)
		for ri := 0; ri < relocs.Count(); ri++ {
			r := relocs.At(ri)
			if r.Type() == objabi.R_WASMIMPORT {
				hostImportMap[r.Sym()] = int64(len(hostImports))
				hostImports = append(hostImports, &wasmFunc{
					Name: ldr.SymName(r.Sym()),
					Type: lookupType(&wasmFuncType{Params: []byte{I32}}, &types),
				})
			}
		}
	}

	// 使用WebAssembly主体收集函数
	var buildid []byte
	fns := make([]*wasmFunc, len(ctxt.Textp))
	for i, fn := range ctxt.Textp {
		wfn := new(bytes.Buffer)
		if ldr.SymName(fn) == "go.buildid" {
			writeUleb128(wfn, 0) // 局部变量集的数量
			writeI32Const(wfn, 0)
			wfn.WriteByte(0x0b) // 终止
			buildid = ldr.Data(fn)
		} else {
			// 重新定位具有可变长度，请在此处进行处理。
			relocs := ldr.Relocs(fn)
			P := ldr.Data(fn)
			off := int32(0)
			for ri := 0; ri < relocs.Count(); ri++ {
				r := relocs.At(ri)
				if r.Siz() == 0 {
					continue // 跳过标记重新定位
				}
				wfn.Write(P[off:r.Off()])
				off = r.Off()
				rs := ldr.ResolveABIAlias(r.Sym())
				switch r.Type() {
				case objabi.R_ADDR:
					writeSleb128(wfn, ldr.SymValue(rs)+r.Add())
				case objabi.R_CALL:
					writeSleb128(wfn, int64(len(hostImports))+ldr.SymValue(rs)>>16-funcValueOffset)
				case objabi.R_WASMIMPORT:
					writeSleb128(wfn, hostImportMap[rs])
				default:
					ldr.Errorf(fn, "bad reloc type %d (%s)", r.Type(), sym.RelocName(ctxt.Arch, r.Type()))
					continue
				}
			}
			wfn.Write(P[off:])
		}

		typ := uint32(0)
		if sig, ok := wasmFuncTypes[ldr.SymName(fn)]; ok {
			typ = lookupType(sig, &types)
		}

		name := nameRegexp.ReplaceAllString(ldr.SymName(fn), "_")
		fns[i] = &wasmFunc{Name: name, Type: typ, Code: wfn.Bytes()}
	}

	ctxt.Out.Write([]byte{0x00, 0x61, 0x73, 0x6d}) // 魔术
	ctxt.Out.Write([]byte{0x01, 0x00, 0x00, 0x00}) // 版本

	// 在二进制文件的早期添加任何buildid：
	if len(buildid) != 0 {
		writeBuildID(ctxt, buildid)
	}

	writeTypeSec(ctxt, types)
	writeImportSec(ctxt, hostImports)
	writeFunctionSec(ctxt, fns)
	writeTableSec(ctxt, fns)
	writeMemorySec(ctxt, ldr)
	writeGlobalSec(ctxt)
	writeExportSec(ctxt, ldr, len(hostImports))
	writeElementSec(ctxt, uint64(len(hostImports)), uint64(len(fns)))
	writeCodeSec(ctxt, fns)
	writeDataSec(ctxt)
	writeProducerSec(ctxt)
	if !*ld.FlagS {
		writeNameSec(ctxt, len(hostImports), fns)
	}
}

func lookupType(sig *wasmFuncType, types *[]*wasmFuncType) uint32 {
	for i, t := range *types {
		if bytes.Equal(sig.Params, t.Params) && bytes.Equal(sig.Results, t.Results) {
			return uint32(i)
		}
	}
	*types = append(*types, sig)
	return uint32(len(*types) - 1)
}

func writeSecHeader(ctxt *ld.Link, id uint8) int64 {
	ctxt.Out.WriteByte(id)
	sizeOffset := ctxt.Out.Offset()
	ctxt.Out.Write(make([]byte, 5)) // 长度占位符
	return sizeOffset
}

func writeSecSize(ctxt *ld.Link, sizeOffset int64) {
	endOffset := ctxt.Out.Offset()
	ctxt.Out.SeekSet(sizeOffset)
	writeUleb128FixedLength(ctxt.Out, uint64(endOffset-sizeOffset-5), 5)
	ctxt.Out.SeekSet(endOffset)
}

func writeBuildID(ctxt *ld.Link, buildid []byte) {
	sizeOffset := writeSecHeader(ctxt, sectionCustom)
	writeName(ctxt.Out, "go.buildid")
	ctxt.Out.Write(buildid)
	writeSecSize(ctxt, sizeOffset)
}

// writeTypeSec编写声明所有函数类型的部分
// 所以它们可以被索引引用。
func writeTypeSec(ctxt *ld.Link, types []*wasmFuncType) {
	sizeOffset := writeSecHeader(ctxt, sectionType)

	writeUleb128(ctxt.Out, uint64(len(types)))

	for _, t := range types {
		ctxt.Out.WriteByte(0x60) // 功能类型
		writeUleb128(ctxt.Out, uint64(len(t.Params)))
		for _, v := range t.Params {
			ctxt.Out.WriteByte(byte(v))
		}
		writeUleb128(ctxt.Out, uint64(len(t.Results)))
		for _, v := range t.Results {
			ctxt.Out.WriteByte(byte(v))
		}
	}

	writeSecSize(ctxt, sizeOffset)
}

// writeImportSec编写列出获取的函数的部分
// 从WebAssembly主机导入，通常为JavaScript。
func writeImportSec(ctxt *ld.Link, hostImports []*wasmFunc) {
	sizeOffset := writeSecHeader(ctxt, sectionImport)

	writeUleb128(ctxt.Out, uint64(len(hostImports))) // 进口数量
	for _, fn := range hostImports {
		writeName(ctxt.Out, "go") // 由wasm_exec.js中的导入对象提供
		writeName(ctxt.Out, fn.Name)
		ctxt.Out.WriteByte(0x00) // func导入
		writeUleb128(ctxt.Out, uint64(fn.Type))
	}

	writeSecSize(ctxt, sizeOffset)
}

// writeFunctionSec编写声明函数类型的部分。
// 这些功能的主体稍后将在“代码”部分中提供。
func writeFunctionSec(ctxt *ld.Link, fns []*wasmFunc) {
	sizeOffset := writeSecHeader(ctxt, sectionFunction)

	writeUleb128(ctxt.Out, uint64(len(fns)))
	for _, fn := range fns {
		writeUleb128(ctxt.Out, uint64(fn.Type))
	}

	writeSecSize(ctxt, sizeOffset)
}

// writeTableSec写入声明表的部分。目前只有一个表
// CallIndirect操作用来动态调用任何函数的。
// 表的内容由“element”部分初始化。
func writeTableSec(ctxt *ld.Link, fns []*wasmFunc) {
	sizeOffset := writeSecHeader(ctxt, sectionTable)

	numElements := uint64(funcValueOffset + len(fns))
	writeUleb128(ctxt.Out, 1)           // 表数
	ctxt.Out.WriteByte(0x70)            // 类型：anyfunc
	ctxt.Out.WriteByte(0x00)            // 没有max
	writeUleb128(ctxt.Out, numElements) // 闵

	writeSecSize(ctxt, sizeOffset)
}

// writeMemorySec编写声明线性内存的部分。目前正在使用一个线性存储器。
// 线性内存总是从地址0开始。可以使用GrowMemory指令请求更多内存。
func writeMemorySec(ctxt *ld.Link, ldr *loader.Loader) {
	sizeOffset := writeSecHeader(ctxt, sectionMemory)

	dataSection := ldr.SymSect(ldr.Lookup("runtime.data", 0))
	dataEnd := dataSection.Vaddr + dataSection.Length
	var initialSize = dataEnd + 16<<20 // 16MB，足够运行时初始化而不增长

	const wasmPageSize = 64 << 10 // 64KB

	writeUleb128(ctxt.Out, 1)                        // 记忆的数量
	ctxt.Out.WriteByte(0x00)                         // 没有最大内存大小
	writeUleb128(ctxt.Out, initialSize/wasmPageSize) // 最小（初始）内存大小

	writeSecSize(ctxt, sizeOffset)
}

// writeGlobalSec编写声明全局变量的部分。
func writeGlobalSec(ctxt *ld.Link) {
	sizeOffset := writeSecHeader(ctxt, sectionGlobal)

	globalRegs := []byte{
		I32, // 0:SP
		I64, // 1:CTXT
		I64, // 2:g
		I64, // 3:RET0
		I64, // 4:RET1
		I64, // 5:RET2
		I64, // 6:RET3
		I32, // 7：暂停
	}

	writeUleb128(ctxt.Out, uint64(len(globalRegs))) // 全局数

	for _, typ := range globalRegs {
		ctxt.Out.WriteByte(typ)
		ctxt.Out.WriteByte(0x01) // 变量
		switch typ {
		case I32:
			writeI32Const(ctxt.Out, 0)
		case I64:
			writeI64Const(ctxt.Out, 0)
		}
		ctxt.Out.WriteByte(0x0b) // 终止
	}

	writeSecSize(ctxt, sizeOffset)
}

// writeExportSec编写声明导出的部分。
// 导出可以由WebAssembly主机访问，通常是JavaScript。
// wasm_export_*函数和线性内存被导出。
func writeExportSec(ctxt *ld.Link, ldr *loader.Loader, lenHostImports int) {
	sizeOffset := writeSecHeader(ctxt, sectionExport)

	writeUleb128(ctxt.Out, 4) // 出口数量

	for _, name := range []string{"run", "resume", "getsp"} {
		s := ldr.Lookup("wasm_export_"+name, 0)
		idx := uint32(lenHostImports) + uint32(ldr.SymValue(s)>>16) - funcValueOffset
		writeName(ctxt.Out, name)           // wasm_exec.js中的inst.exports.run/resume/getsp
		ctxt.Out.WriteByte(0x00)            // func导出
		writeUleb128(ctxt.Out, uint64(idx)) // funcidx
	}

	writeName(ctxt.Out, "mem") // wasm_exec.js中的inst.exports.mem
	ctxt.Out.WriteByte(0x02)   // mem导出
	writeUleb128(ctxt.Out, 0)  // memidx

	writeSecSize(ctxt, sizeOffset)
}

// writeElementSec写入初始化“table”部分声明的表的部分。
// CallIndirect的表以一种非常简单的方式初始化，以便每个表索引（PC_F值）
// 线性映射到函数索引（numImports+PC_F）。
func writeElementSec(ctxt *ld.Link, numImports, numFns uint64) {
	sizeOffset := writeSecHeader(ctxt, sectionElement)

	writeUleb128(ctxt.Out, 1) // 元素段数

	writeUleb128(ctxt.Out, 0) // 表IDX
	writeI32Const(ctxt.Out, funcValueOffset)
	ctxt.Out.WriteByte(0x0b) // 终止

	writeUleb128(ctxt.Out, numFns) // 参赛人数
	for i := uint64(0); i < numFns; i++ {
		writeUleb128(ctxt.Out, numImports+i)
	}

	writeSecSize(ctxt, sizeOffset)
}

// writeElementSec编写为函数提供函数体的部分
// 由“func”部分声明。
func writeCodeSec(ctxt *ld.Link, fns []*wasmFunc) {
	sizeOffset := writeSecHeader(ctxt, sectionCode)

	writeUleb128(ctxt.Out, uint64(len(fns))) // 代码条目数
	for _, fn := range fns {
		writeUleb128(ctxt.Out, uint64(len(fn.Code)))
		ctxt.Out.Write(fn.Code)
	}

	writeSecSize(ctxt, sizeOffset)
}

// writeDataSec写入提供用于初始化线性内存的数据的部分。
func writeDataSec(ctxt *ld.Link) {
	sizeOffset := writeSecHeader(ctxt, sectionData)

	type dataSegment struct {
		offset int32
		data   []byte
	}

	// 忽略零块，而是发射偏移量跳过零的数据段。
	// 这将减小WebAssembly二进制文件的大小。我们使用8个字节作为
	// 添加新段的开销（与wasm opt的内存打包优化使用相同）。
	const segmentOverhead = 8

	// 最多生成这么多段。某些WebAssembly运行时会拒绝更多的段。
	const maxNumSegments = 100000

	var segments []*dataSegment
	for secIndex, ds := range dataSects {
		data := ds.data
		offset := int32(ds.sect.Vaddr)

		// 跳过前导零
		for len(data) > 0 && data[0] == 0 {
			data = data[1:]
			offset++
		}

		for len(data) > 0 {
			dataLen := int32(len(data))
			var segmentEnd, zeroEnd int32
			if len(segments)+(len(dataSects)-secIndex) == maxNumSegments {
				segmentEnd = dataLen
				zeroEnd = dataLen
			} else {
				for {
					// 寻找零的开头
					for segmentEnd < dataLen && data[segmentEnd] != 0 {
						segmentEnd++
					}
					// 寻找零的结尾
					zeroEnd = segmentEnd
					for zeroEnd < dataLen && data[zeroEnd] == 0 {
						zeroEnd++
					}
					// 如果省略零，则发射段将减小输出大小
					if zeroEnd-segmentEnd >= segmentOverhead || zeroEnd == dataLen {
						break
					}
					segmentEnd = zeroEnd
				}
			}

			segments = append(segments, &dataSegment{
				offset: offset,
				data:   data[:segmentEnd],
			})
			data = data[zeroEnd:]
			offset += zeroEnd
		}
	}

	writeUleb128(ctxt.Out, uint64(len(segments))) // 数据条目的数目
	for _, seg := range segments {
		writeUleb128(ctxt.Out, 0) // memidx
		writeI32Const(ctxt.Out, seg.offset)
		ctxt.Out.WriteByte(0x0b) // 终止
		writeUleb128(ctxt.Out, uint64(len(seg.data)))
		ctxt.Out.Write(seg.data)
	}

	writeSecSize(ctxt, sizeOffset)
}

// writeProducerSec编写一个可选部分，报告源语言和编译器版本。
func writeProducerSec(ctxt *ld.Link) {
	sizeOffset := writeSecHeader(ctxt, sectionCustom)
	writeName(ctxt.Out, "producers")

	writeUleb128(ctxt.Out, 2) // 字段数

	writeName(ctxt.Out, "language")       // 字段名
	writeUleb128(ctxt.Out, 1)             // 值的数目
	writeName(ctxt.Out, "Go")             // 值：name
	writeName(ctxt.Out, buildcfg.Version) // 值：版本

	writeName(ctxt.Out, "processed-by")   // 字段名
	writeUleb128(ctxt.Out, 1)             // 值的数目
	writeName(ctxt.Out, "Go cmd/compile") // 值：name
	writeName(ctxt.Out, buildcfg.Version) // 值：版本

	writeSecSize(ctxt, sizeOffset)
}

var nameRegexp = regexp.MustCompile(`[^\w\.]`)

// writeNameSec编写一个可选部分，为“func”部分声明的函数指定名称。
// 这些名称仅由WebAssembly堆栈跟踪、调试器和反编译器使用。
// TODO（neelance）：添加数据符号的符号表
func writeNameSec(ctxt *ld.Link, firstFnIndex int, fns []*wasmFunc) {
	sizeOffset := writeSecHeader(ctxt, sectionCustom)
	writeName(ctxt.Out, "name")

	sizeOffset2 := writeSecHeader(ctxt, 0x01) // 函数名
	writeUleb128(ctxt.Out, uint64(len(fns)))
	for i, fn := range fns {
		writeUleb128(ctxt.Out, uint64(firstFnIndex+i))
		writeName(ctxt.Out, fn.Name)
	}
	writeSecSize(ctxt, sizeOffset2)

	writeSecSize(ctxt, sizeOffset)
}

type nameWriter interface {
	io.ByteWriter
	io.Writer
}

func writeI32Const(w io.ByteWriter, v int32) {
	w.WriteByte(0x41) // i32.const
	writeSleb128(w, int64(v))
}

func writeI64Const(w io.ByteWriter, v int64) {
	w.WriteByte(0x42) // i64.const
	writeSleb128(w, v)
}

func writeName(w nameWriter, name string) {
	writeUleb128(w, uint64(len(name)))
	w.Write([]byte(name))
}

func writeUleb128(w io.ByteWriter, v uint64) {
	if v < 128 {
		w.WriteByte(uint8(v))
		return
	}
	more := true
	for more {
		c := uint8(v & 0x7f)
		v >>= 7
		more = v != 0
		if more {
			c |= 0x80
		}
		w.WriteByte(c)
	}
}

func writeUleb128FixedLength(w io.ByteWriter, v uint64, length int) {
	for i := 0; i < length; i++ {
		c := uint8(v & 0x7f)
		v >>= 7
		if i < length-1 {
			c |= 0x80
		}
		w.WriteByte(c)
	}
	if v != 0 {
		panic("writeUleb128FixedLength: length too small")
	}
}

func writeSleb128(w io.ByteWriter, v int64) {
	more := true
	for more {
		c := uint8(v & 0x7f)
		s := uint8(v & 0x40)
		v >>= 7
		more = !((v == 0 && s == 0) || (v == -1 && s != 0))
		if more {
			c |= 0x80
		}
		w.WriteByte(c)
	}
}
